package org.itsolutions.javaBackup.utils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;

import org.itsolutions.javaBackup.exceptions.AException;
import org.itsolutions.javaBackup.exceptions.CommonException;
import org.itsolutions.javaBackup.logger.ELogLevel;
import org.itsolutions.javaBackup.logger.ILogger;
import org.itsolutions.javaBackup.logger.LoggerHelper;

/**
 * File helper methods.
 * @author Tomas Fecko
 */
public final class FileUtils {

    private static final ILogger LOGGER = LoggerHelper.getLogger(FileUtils.class);

    /**
     *
     */
    private FileUtils() {
    }

    /**
     * @param pDirectory directory to delete
     * @return return true if success
     */
    public static boolean deleteDir(File pDirectory) {
        if (pDirectory.isDirectory()) {
            String[] children = pDirectory.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(pDirectory, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        return pDirectory.delete();
    }

    /**
     * @param pPathToDir path to dir
     * @return list of full path to files in all directories recursively
     */
    public static List<String> getFileNames(String pPathToDir) {
        File dir = new File(pPathToDir);
        List<String> returnList = new ArrayList<String>();
        if (dir != null && dir.exists() && dir.isDirectory()) {
            getFileNames(dir, returnList);
        }
        return returnList;
    }

    /**
     * @param pFileURL           source file name from the classpath
     * @param pNameOfTheDirInTmp dir in tmp dir
     * @param pDestFileName      destination file name
     * @return absolute path of dest file
     * @throws AException if error during copy
     */
    public static String copyFileFromClassPathToTemp(URL pFileURL, String pNameOfTheDirInTmp, String pDestFileName) throws AException {
        String fileSeparator = System.getProperty("file.separator");
        String tmpDirStr = System.getProperty("java.io.tmpdir") + fileSeparator + pNameOfTheDirInTmp;
        File tmpDir = new File(tmpDirStr);
        if (!tmpDir.exists() && !tmpDir.mkdirs()) {
            throw new CommonException("Dir: " + tmpDirStr + " can't be created...");
        }
        try {
            String destFileName = tmpDir.getAbsolutePath() + fileSeparator + pDestFileName;
            File destFile = new File(destFileName);
            if (destFile.exists() && !destFile.delete()) {
                throw new CommonException("Destination file: " + destFileName + " exists and can't be deleted...");
            }
            if (pFileURL.toURI().isOpaque()) {
                InputStream sourceStream = pFileURL.openStream();
                copyFile(sourceStream, destFile);
            } else {
                File sourceFile = new File(pFileURL.toURI());
                if (sourceFile.exists()) {
                    copyFile(sourceFile, destFile);
                } else {
                    throw new CommonException("Source file doesn't exists.");
                }
            }
            return destFile.getAbsolutePath();
        } catch (Exception e) {
            throw new CommonException(e);
        }
    }

    /**
     * Copy files.
     *
     * @param pSource      source file
     * @param pDestination destination file
     * @throws AException exception during copy
     */
    public static void copyFile(File pSource, File pDestination) throws AException {
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            inChannel = new FileInputStream(pSource).getChannel();
            outChannel = new FileOutputStream(pDestination).getChannel();
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } catch (IOException e) {
            throw new CommonException(e);
        } finally {
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    throw new CommonException(e);
                }
            }
            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    throw new CommonException(e);
                }
            }
        }
    }

    /**
     * Copy from input stream to file, and closes input stream.
     *
     * @param pSourceIS    source file
     * @param pDestination destination file
     * @throws AException exception during copy
     */
    public static void copyFile(InputStream pSourceIS, File pDestination) throws AException {
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(pDestination);
            //CHECKSTYLE:OFF
            byte[] buf = new byte[4096];
            //CHECKSTYLE:ON
            int i = 0;
            while ((i = pSourceIS.read(buf)) != -1) {
                fos.write(buf, 0, i);
            }
        } catch (IOException e) {
            throw new CommonException(e);
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    throw new CommonException(e);
                }
            }
            if (pSourceIS != null) {
                try {
                    pSourceIS.close();
                } catch (IOException e) {
                    throw new CommonException(e);
                }
            }
        }
    }

    /**
     * @param pDir
     * @param pListToFillIn
     * @return
     */
    private static List<String> getFileNames(File pDir, List<String> pListToFillIn) {
        String[] list = pDir.list();
        if (list != null && list.length > 0) {
            for (String name : list) {
                String pathname = new StringBuilder(pDir.getAbsolutePath()).append(System.getProperty("file.separator")).append(name).toString();
                File f = new File(pathname);
                if (f.isDirectory()) {
                    getFileNames(f, pListToFillIn);
                } else if (f.isFile()) {
                    pListToFillIn.add(pathname);
                }
            }
        }
        return pListToFillIn;
    }


    /**
     * @param pDirInTempToLoadFrom dir where the library will be copied in temp and loaded from
     * @param pLibraryFileName     library file name
     * @throws AException exception during the loading
     */
    public static void loadLibrary(String pDirInTempToLoadFrom, String pLibraryFileName) throws AException {
        URL fileURL = Thread.currentThread().getContextClassLoader().getResource(pLibraryFileName);
        String destFileName = FileUtils.copyFileFromClassPathToTemp(fileURL, pDirInTempToLoadFrom, pLibraryFileName);
        System.load(destFileName);
    }

    /**
     * @param pNewPath new path to set
     * @throws AException exception if setting goes wrong
     */
    public static void initJavaLibraryPath(String pNewPath) throws AException {
        try {
            System.setProperty("java.library.path", pNewPath);
            Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
            fieldSysPath.setAccessible(true);
            fieldSysPath.set(null, null);
        } catch (Exception e) {
            throw new CommonException(e);
        }
    }

    /**
     * Write provided lines to a file and afterward close the file.
     *
     * @param pFile  you want these lines write to
     * @param pLines you want to add to file
     * @throws IOException when cannot create this file
     */
    public static void writeToFile(String pFile, String... pLines) throws IOException {
        File file = new File(pFile);
        if (null != file.getParentFile() && !file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }

        BufferedWriter mWriter = new BufferedWriter(new java.io.FileWriter(file));

        for (String line : pLines) {
            mWriter.write(line + "\n");
        }

        mWriter.flush();
        mWriter.close();
    }

    /**
     * 
     * @param pFile file
     * @param pSearchString searched string
     * @param pReplaceString string for replace
     */
    public static void filterFileContent(File pFile, String pSearchString, String pReplaceString) {
        byte[] buffer = new byte[(int) pFile.length()];
        PrintWriter out = null;
        try {
            FileInputStream f = new FileInputStream(pFile);
            f.read(buffer);
            f.close();

            String content = new String(buffer);
            content = content.replaceAll(pSearchString, pReplaceString);

            out = new PrintWriter(new FileOutputStream(pFile));
            out.print(content);
        } catch (IOException e) {
            LOGGER.log(ELogLevel.ERROR, e);
        } finally {
            if (null != out) {
                out.close();
            }
        }
    }

    /**
     * 
     * @param pStringFile file uri
     * @return file
     * @throws URISyntaxException if uri is bad 
     */
    public static File getFileFromClasspath(String pStringFile) throws URISyntaxException {
        return new File(ClassLoader.getSystemClassLoader().getResource(pStringFile).toURI());
    }
}






